<?php
/**
 * jwt封装类
 *
 * @author dior <diaoxingkwan@vip.qq.com>
 *
 * @date 2021/06/01
 */

namespace app\common\util;

use DateInterval;
use DateTimeImmutable;
use DateTimeZone;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key;


class Jwt
{
    //密钥
    private $key = 'git-dior-key';
    //过期时间 2小时
    private $expireTime = 60 * 60 * 2;
    //签发人
    private $issuer = 'dior-send';
    //接收人
    private $audience = 'dior-accept';
    //唯一标识
    private $identified = 'dior';
    //配置对象
    private $config = null;
    //加密类型
    private $signer = null;

    public function __construct()
    {
        $this->setSigner();
    }

    /**
     * 配置信息
     * @return Configuration
     */
    private function getConfig(): Configuration
    {
        if (!$this->config) {
            $this->config = Configuration::forSymmetricSigner($this->signer, new Key($this->key));
        }
        return $this->config;
    }

    /**
     * 创建
     * @param array $data
     * @return string
     */
    public function createJwt(array $data): string
    {
        //签发时间
        $now = new DateTimeImmutable();

        $token = $this->handler()
            ->issuedBy($this->issuer)
            ->permittedFor($this->audience)
            ->identifiedBy($this->identified)
            ->issuedAt($now)
            ->expiresAt($now->modify('+' . $this->expireTime . ' seconds'))
            ->withClaim('data', json_encode($data))
            ->getToken($this->getConfig()->signer(), $this->getConfig()->signingKey());

        return (string)$token;
    }

    /**
     * 校验jwt
     * @param bool $isReturnError 是否返回错误信息
     * @param string $token token
     * @return false|array
     */
    public function verifyJwt(bool $isReturnError = true, string $token = '')
    {
        if ($token === '') {
            $token = request()->header('Authorization');
            if (empty($token)) {
                return false;
            }
        }

        $token = $this->getConfig()->parser()->parse($token);

        //验证jwt id是否匹配
        if (!$token->isIdentifiedBy($this->identified)) {
            return $isReturnError === true ? '唯一标识校验不通过' : false;
        }

        //验证签发人url是否正确
        if (!$token->hasBeenIssuedBy($this->issuer)) {
            return $isReturnError === true ? '签发人校验不通过' : false;
        }

        //验证客户端url是否匹配
        if (!$token->isPermittedFor($this->audience)) {
            return $isReturnError === true ? '客户端校验不通过' : false;
        }

        //验证时间
        $now = (new SystemClock(new DateTimeZone('Asia/Shanghai')))->now();
        $leeway = new DateInterval('PT0S');

        if (!$token->hasBeenIssuedBefore($now->add($leeway))) {
            return $isReturnError === true ? '该token未发布' : false;
        }
        if (!$token->isMinimumTimeBefore($now->add($leeway))) {
            return $isReturnError === true ? 'token还不能使用' : false;
        }
        if ($token->isExpired($now->sub($leeway))) {
            return $isReturnError === true ? 'token已过期' : false;
        }

        //验证签名
        if ($token->headers()->get('alg') !== $this->getConfig()->signer()->getAlgorithmId()) {
            return $isReturnError === true ? 'token加密类型不正确' : false;
        }
        if (!$this->getConfig()->signer()->verify((string)$token->signature(), $token->payload(), $this->getConfig()->signingKey())) {
            return $isReturnError === true ? '签名校验失败' : false;
        }

        return json_decode($token->claims()->get('data'), true);
    }

    /**
     * 设置加密类型
     * @return $this
     */
    private function setSigner(): Jwt
    {
        $this->signer = new Sha256();
        return $this;
    }

    /**
     * 设置密钥
     * @param string $key 密钥字符串
     * @return $this
     */
    public function setKey(string $key): Jwt
    {
        $this->key = $key;
        return $this;
    }

    /**
     * 设置过期时间
     * @param int $expireTime 过期时间，单位（秒）
     * @return $this
     */
    public function setExpire(int $expireTime): Jwt
    {
        $this->expireTime = $expireTime;
        return $this;
    }

    /**
     * 设置签发人
     * @param string $issuer 签发人
     * @return $this
     */
    public function setIssuer(string $issuer): Jwt
    {
        $this->issuer = $issuer;
        return $this;
    }

    /**
     * 获取builder
     * @return Builder
     */
    private function handler(): Builder
    {
        return $this->getConfig()->builder();
    }
}